package gm.tcpping;

import java.util.regex.*;

public class TCPPing {

    // ===========================================================
    // Constants
    // ===========================================================

    private static final int DEFAULT_MPS = 1;
    private static final int DEFAULT_MESSAGE_SIZE = 300;
    
    // NOTE: I didn't put MIN_MESSAGE_SIZE to 50 because Message object has serialization size 154 bytes
    // or something like that
    // I could change the object to have less fields (for example it doesn't need any pitcher times,
    // some variables could be int or short instead of long etc.) but I don't see the point now :)
    private static final int MIN_MESSAGE_SIZE = 200;
    private static final int MAX_MESSAGE_SIZE = 3000;
    
    // generic value used for input and filler buffers
    public static final int BUFFER_SIZE = 10000;
    
    // ===========================================================
    // Fields
    // ===========================================================

    private static Pitcher mPitcher;
    private static Catcher mCatcher;

    // ===========================================================
    // Methods
    // ===========================================================
    
    /**
     * @param args
     */
    public static void main(String[] args) {
        mPitcher = null;
        mCatcher = null;
        
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                cleanup();
            }
        });
        
        if (args.length == 0) {
            printUsage(false);
            return;
        }
        
        if (args[0].equals("-p")) {
            Pitcher pitcher = createPitcher(args);
            if (pitcher != null) {
                pitcher.start();
            } else {
                printUsage(true);
            }
        } else if (args[0].equals("-c")) {
            Catcher catcher = createCatcher(args);
            if (catcher != null) {
                catcher.start();
            } else {
                printUsage(true);
            }
        } else {
        	printUsage(true);
        }
    }
    
    private static void cleanup() {
        if (mPitcher != null) {
            mPitcher.stop();
        }
        if (mCatcher != null) {
            mCatcher.stop();
        }
    }
    
    private static void printUsage(boolean isArgsError) {
        if (isArgsError) {
            System.out.println("Error - invalid arguments!");
            System.out.println();
        }
        
        System.out.println("Usage: java TCPPing -p [-pitcher_param] <hostname>");
        System.out.println("           (to run in pitcher mode)");
        System.out.println("   or: java TCPPing -c [-catcher_params]");
        System.out.println("           (to run in catcher mode)");
        System.out.println();
        System.out.println("Pitcher parameters:");
        System.out.println("    -port <port>         required");
        System.out.println("    -mps <rate>          optional");
        System.out.println("    -size <size>         optional");
        System.out.println();
        System.out.println("Catcher parameters:");
        System.out.println("    -bind <ip_address>   required");
        System.out.println("    -port <port>         required");
    }
    
    private static Pitcher createPitcher(String[] args) {
        int port = getPort(args);
        if (port == -1) {
            return null;
        }
        
        int mps = getMps(args);
        if (mps == -1) {
            return null;
        }
        
        int size = getSize(args);
        if (size == -1) {
            return null;
        }
        
        String hostname = getHostname(args);
        if (hostname == null) {
            return null;
        }
        
        return new Pitcher(port, mps, size, hostname);
    }
    
    private static Catcher createCatcher(String[] args) {
        int port = getPort(args);
        if (port == -1) {
            return null;
        }
        
        String ipAddress = getIPAddress(args);
        if (ipAddress == null) {
            return null;
        }
        
        return new Catcher(port, ipAddress);
    }
    
    /* Parameter Parsing */
    private static int getPort(String[] args) {
        int paramIndex = getParamIndex("-port", args);
        if (paramIndex == -1 || paramIndex >= args.length - 1) {
            return -1;
        }
        
        Integer port = TCPPingUtility.tryParse(args[paramIndex + 1]);
        if (port != null) {
            return port.intValue();
        } else {
            return -1;
        }
    }
    
    private static int getMps(String[] args) {
        int paramIndex = getParamIndex("-mps", args);
        if (paramIndex == -1) {
            return DEFAULT_MPS;
        } else if (paramIndex >= args.length - 1) {
            return -1;
        }
        
        Integer mps = TCPPingUtility.tryParse(args[paramIndex + 1]);
        if (mps != null) {
            return mps.intValue();
        } else {
            return -1;
        }
    }
    
    private static int getSize(String[] args) {
        int paramIndex = getParamIndex("-size", args);
        if (paramIndex == -1) {
            return DEFAULT_MESSAGE_SIZE;
        } else if (paramIndex >= args.length - 1) {
            return -1;
        }
        
        Integer size = TCPPingUtility.tryParse(args[paramIndex + 1]);
        if (size != null) {
            return TCPPingUtility.clampInt(size.intValue(), MIN_MESSAGE_SIZE, MAX_MESSAGE_SIZE);
        } else {
            return -1;
        }
    }
    
    private static String getHostname(String[] args) {
        // IP address will pass pattern check and can be used as hostname
        Pattern pattern = Pattern.compile(
                "^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\\-]*[A-Za-z0-9])$|" +
                "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$",
                Pattern.CASE_INSENSITIVE);

        String hostname = args[args.length - 1];
        Matcher matcher = pattern.matcher(hostname);
        if (matcher.matches()) {
            return hostname;
        } else {
            return null;
        }
    }
    
    private static String getIPAddress(String[] args) {
        Pattern pattern = Pattern.compile(
                "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");

        int paramIndex = getParamIndex("-bind", args);
        if (paramIndex == -1 || paramIndex >= args.length - 1) {
            return null;
        }
        
        String ipAddress = args[paramIndex + 1];
        Matcher matcher = pattern.matcher(ipAddress);
        if (matcher.matches()) {
            return ipAddress;
        } else {
            return null;
        }
    }
    
    private static int getParamIndex(String paramName, String[] args) {
        for (int i = 0; i < args.length; i++) {
            if (args[i].equals(paramName)) {
                return i;
            }
        }
        
        return -1;
    }
    /* End Parameter Parsing */
}